﻿using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using Point = Microsoft.Xna.Framework.Point;
using Color = Microsoft.Xna.Framework.Color;
using MouseButtonEventArgs = xEngine.Input.MouseButtonEventArgs;
using Keys = Microsoft.Xna.Framework.Input.Keys;
using Convert = xEngine.Utils.Convert;

using xEngine.UI;
using xEngine.Debugging;
using xEngine.Actors;
using xEngine.Actors.Terrain;
using xEngine.Utils;
using xEngine.Input;
using xEngine.Rendering;
using xEngine;
using xEngine.Maths;
using xEngine.DataTypes;

using xEditor.UI;

namespace xEditor
{    
    class RenderingControl : XControl
    {
        #region Member Variables

        private List<Actor> _selectedActors;

        private Camera _camera;

        private bool _cameraBoost;
        private float _lastFrameTime;

        // TEMPS
        private BasicEffect _fx;

        #endregion

        #region Properties
        
        public float CameraSpeed { get; set; }
        public float CameraSpeedBoost { get; set; }
        public List<Actor> Actors { get; private set; }
        public SceneMode SceneMode { get; set; }

        #endregion

        #region Constructors

        public RenderingControl()
        {
            _camera = new Camera();
            _camera.Far = 10000;
            _camera.Transformation.OnMatrixInvalidated += new EventHandler(delegate { if (OnCameraMoved != null) OnCameraMoved(_camera, EventArgs.Empty); });
            _camera.RestrictX = new Vector2(-MathHelper.PiOver2, MathHelper.PiOver2);

            Actors = new List<Actor>();
            _selectedActors = new List<Actor>();

            BackColor = System.Drawing.Color.CornflowerBlue;
            XConsole.Owner = this;
            XConsole.MaxEntries = 1000;
            
            CameraSpeed = 500.0f;
            CameraSpeedBoost = 1000.0f;
        }

        #endregion

        #region Functions

        // Generic
        public void ClearScene()
        {
            Actor[] actors = Actors.ToArray();

            foreach (Actor actor in actors)
                RemoveActor(actor);
        }

        // Actor related
        public Actor[] FindActorsOfType(string type)
        {
            List<Actor> actors = new List<Actor>();

            foreach (Actor actor in Actors)
                if (actor.GetType().Name.ToLower() == type.ToLower())
                    actors.Add(actor);

            return actors.Count > 0 ? actors.ToArray() : null;
        }
        public void AddActor(Actor actor)
        {
            if (actor == null)
                return;

            Actors.Add(actor);

            if (OnActorAdded != null)
                OnActorAdded(actor, EventArgs.Empty);
        }
        public void RemoveActor(Actor actor)
        {
            if (actor == null)
                return;

            Actors.Remove(actor);

            if (_selectedActors.Contains(actor))
                _selectedActors.Remove(actor);

            if (OnActorRemoved != null)
                OnActorRemoved(actor, EventArgs.Empty);

            actor.Dispose();
        }

        // Selection
        public void AddSelection(Actor actor)
        {
            if (actor == null)
                return;

            if(actor.GetType() == typeof(Terrain))
                ((Terrain)actor).TerrainCursor.Show = true;

            _selectedActors.Add(actor);
        }
        public void RemoveSelection(Actor actor)
        {
            if (actor.GetType() == typeof(Terrain))
                ((Terrain)actor).TerrainCursor.Show = false;

            _selectedActors.Remove(actor);
        }
        public void ClearSelection()
        {
            foreach(Actor actor in _selectedActors)
                if (actor.GetType() == typeof(Terrain))
                    ((Terrain)actor).TerrainCursor.Show = false;

            _selectedActors.Clear();
        }
        public Actor[] GetSelection()
        {
            return _selectedActors.ToArray();
        }
        private void ActorSelected()
        {
            if (OnActorSelected != null)
                OnActorSelected(null, EventArgs.Empty);
        }

        // Terrain
        public Terrain CreateNewTerrain(int res)
        {
            Terrain t = new Terrain(GraphicsDevice, res);
            t.Layers.Add(new TerrainLayer(Content.Load<Texture2D>("textures/defaultnouvs"), Vector2.Zero, Vector2.One, Vector4.One));
            t.Layers.Add(new TerrainLayer(Content.Load<Texture2D>("textures/defaultnouvs"), Vector2.Zero, Vector2.One * 32, Vector4.One));
            t.TerrainCursor.Texture = Content.Load<Texture2D>("textures/terrain_cursor");
            t.TerrainCursor.Color = Color.Red.ToVector4();
            AddActor(t);
            return t;
        }
        public Terrain LoadHeightMap(string path, HeightMapType type)
        {
            Terrain t = new Terrain(GraphicsDevice, path, type);
            t.Layers.Add(new TerrainLayer(Content.Load<Texture2D>("../textures/defaultnouvs"), Vector2.Zero, Vector2.One, Vector4.One));
            t.Layers.Add(new TerrainLayer(Content.Load<Texture2D>("../textures/defaultnouvs"), Vector2.Zero, Vector2.One * 32, Vector4.One));
            t.TerrainCursor.Texture = Content.Load<Texture2D>("../textures/terrain_cursor");
            t.TerrainCursor.Color = Color.Red.ToVector4();
            AddActor(t);
            return t;
        }

        protected override void OnResize(EventArgs e)
        {
            _camera.FoV = (float)Math.Atan(_camera.Aspect = (float)Math.Max(Width, 1) / Math.Max(Height, 1));

            base.OnResize(e);
        }
        protected override void Initialize()
        {
            KeyboardHandler();
            
            _fx = new BasicEffect(GraphicsDevice);
            _fx.SpecularColor = Vector3.Zero;
            _fx.EnableDefaultLighting();
            _fx.DiffuseColor = GenericResources.Colors.LightBlue.ToVector3();

            _camera.Transformation.Position = new Vector3(0, 1000, 0);
            _camera.Transformation.Direction = new Vector3(1, -0.4f, 1);
        }
        protected override void Update(GameTime gameTime)
        {
            if (!Initialized)
                return;

            _lastFrameTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            _fx.View = _camera.View;
            _fx.Projection = _camera.Projection;

            foreach (Actor actor in Actors)
                actor.Update(gameTime);

            _cameraBoost = false;
        }
        protected override void Render(GameTime gameTime)
        {
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            foreach (Actor actor in Actors)
            {
                actor.DefaultShader.Apply(_camera);
                actor.Render();
            }

            // Render actor selections 
            //GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.None, FillMode = FillMode.WireFrame };            
            //foreach(Actor actor in _selectedActors)
            //{
            //    _fx.World = actor.Transformation.Matrix;
            //    _fx.CurrentTechnique.Passes[0].Apply();
            //    actor.Render();
            //}
            //GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.None, FillMode = FillMode.Solid };
        }
        protected override void Dispose(bool disposing)
        {
            foreach (Actor actor in Actors)
                actor.Dispose();

            base.Dispose(disposing);  
        }

        private Actor CheckSelectionClosest(Ray ray)
        {
            Actor selected = null;
            IntersectionResult res;
            float distance = 0xFFFFFFFF;

            foreach (Actor actor in Actors)
                if ((res = actor.Collides(ray)).Intersects)
                {
                    if (distance > (float)res.Param)
                    {
                        selected = actor;
                        distance = (float)res.Param;
                    }
                }

            return selected;
        }

        #endregion

        #region Events

        public event EventHandler OnActorAdded;
        public event EventHandler OnActorRemoved;
        public event EventHandler OnActorSelected;
        public event EventHandler OnActorUpdated;
        public event EventHandler OnCameraMoved;

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            Input.WindowCenter = e.Position;

            if (e.Left.WasPressed())
            {
                Actor selected = CheckSelectionClosest(RenderManager.ProjectRayFromScreenPoint(PointToClient(new System.Drawing.Point(e.Position.X, e.Position.Y)), _camera));

                if (Input.IsKeyDown(Keys.LeftControl) || Input.IsKeyDown(Keys.RightControl))
                {
                    if (selected != null)
                    {
                        if (_selectedActors.Contains(selected))
                            RemoveSelection(selected);
                        else
                            AddSelection(selected);
                    }
                }
                else
                {
                    ClearSelection();
                    AddSelection(selected);
                }

                ActorSelected();
            }
        }
        protected override void OnMouseMoved(MouseMovedEventArgs e)
        {
            e.ResetToCenter = false;

            if (!Focused)
                return;

            if (InputManager.IsMouseButton(e.PressedButtons, MouseButton.Right))
            {
                _camera.Rotate(-e.Delta.Y * 0.003f, -e.Delta.X * 0.003f);
                e.ResetToCenter = true;
            }

            if (InputManager.IsMouseButton(e.PressedButtons, MouseButton.Left))
            {
                Vector3 v;

                switch (SceneMode)
                {
                    case SceneMode.Move:
                        v = Vector3.Transform(new Vector3(e.Delta.X, -e.Delta.Y, 0), _camera.Transformation.RotationM);
                        foreach (Actor actor in _selectedActors)
                            actor.Transformation.Position += v;
                        break;

                    case SceneMode.Rotate:
                        v = Vector3.Transform(new Vector3(e.Delta.Y, e.Delta.X, 0), _camera.Transformation.RotationM) * 0.01f;
                        foreach (Actor actor in _selectedActors)
                            actor.Transformation.Rotation += v;
                        break;

                    case SceneMode.Scale:
                        v = Vector3.Transform(new Vector3(e.Delta.X, -e.Delta.Y, 0), _camera.Transformation.RotationM) * 0.01f;
                        foreach (Actor actor in _selectedActors)
                            actor.Transformation.Scale += v;
                        break;
                }

                if (OnActorUpdated != null)
                    foreach (Actor actor in _selectedActors)
                        OnActorUpdated(actor, EventArgs.Empty);

                e.ResetToCenter = true;
            }

            if (InputManager.IsMouseButton(e.PressedButtons, MouseButton.None))
            {
                Ray ray = RenderManager.ProjectRayFromScreenPoint(PointToClient(new System.Drawing.Point(e.Position.X, e.Position.Y)), _camera);
                IntersectionResult res;
                Vector3 v;

                foreach (Actor actor in Actors)
                    if (actor.GetType() == typeof(Terrain) && (res = actor.Collides(ray)).Intersects)
                    {
                        v = ray.Position + ray.Direction * ((float)res.Param);
                        ((Terrain)actor).TerrainCursor.Position = Convert.V3ToV2xz(v);

                        OSD.Add("CursorPosition", ((Terrain)actor).TerrainCursor.Position);
                        OSD.Add("v", v);
                    }
            }
        }
        protected override void OnMouseLeave(EventArgs e)
        {
            Parent.SelectNextControl(this, true, false, true, true);
        }
        private void KeyboardHandler()
        {
            Input.AddKeyEventHandler(Keys.LeftShift, delegate {
                _cameraBoost = true;
            });
            Input.AddKeyEventHandler(Keys.W, delegate {
                if (Focused)
                    _camera.Move(new Vector3(0, 0, -(_cameraBoost ? CameraSpeedBoost : CameraSpeed) * _lastFrameTime));
            });
            Input.AddKeyEventHandler(Keys.S, delegate {
                if (Focused)
                    _camera.Move(new Vector3(0, 0, (_cameraBoost ? CameraSpeedBoost : CameraSpeed) * _lastFrameTime));
            });
            Input.AddKeyEventHandler(Keys.A, delegate {
                if (Focused)
                    _camera.Move(new Vector3(-(_cameraBoost ? CameraSpeedBoost : CameraSpeed) * _lastFrameTime, 0, 0));
            });
            Input.AddKeyEventHandler(Keys.D, delegate {
                if (Focused)
                    _camera.Move(new Vector3((_cameraBoost ? CameraSpeedBoost : CameraSpeed) * _lastFrameTime, 0, 0));
            });
        }

        #endregion
    }

    public enum SceneMode
    {
        Select,
        Move,
        Rotate,
        Scale
    }
}
